Skip to content

feat: add torph text morphing animations#278

Draft
paulbalaji wants to merge 7 commits intomainfrom
feat/torph-text-animations
Draft

feat: add torph text morphing animations#278
paulbalaji wants to merge 7 commits intomainfrom
feat/torph-text-animations

Conversation

@paulbalaji
Copy link
Collaborator

@paulbalaji paulbalaji commented Feb 17, 2026

Summary

  • Added torph@0.0.5 for text morphing animations.
  • Created SafeTextMorph wrapper component with ErrorBoundary.
  • Integrated animated text in explorer search/message surfaces.
  • Follow-up from review:
    • fallback now preserves as / className / style and recovers after transient errors,
    • removed morph wrapper from static heading text,
    • aligned jest + jest-environment-jsdom major versions,
    • fixed ts-node override compatibility with moduleResolution: bundler,
    • cleaned conditional class handling / formatting in message table.

What is torph?

torph is a dependency-free, FLIP-based text morphing library for React. It animates changes by moving shared characters and fading entering/exiting characters.

Integration Points (Practical Expected Behavior)

  • SearchFilterBar.tsx: selected filter labels (chain/status) animate when filters change.
  • MessageDetails.tsx: dynamic status text animates; static top heading remains static.
  • MessageTable.tsx: origin/destination chain labels animate on row/filter updates instead of abrupt text swaps.
  • Failure mode: if animation render fails, fallback text preserves element type/style and UI stays stable.

Config Changes

  • tsconfig.json: keep project moduleResolution: bundler and set ts-node.compilerOptions.moduleResolution to node for script/tool compatibility.
  • package.json / lockfile: align jest-environment-jsdom with Jest 29 major.

Validation

  • pnpm run test -- src/components/SafeTextMorph.test.tsx
  • pnpm run typecheck
  • pnpm exec eslint src/components/SafeTextMorph.tsx src/components/SafeTextMorph.test.tsx src/features/messages/MessageDetails.tsx src/features/messages/MessageTable.tsx

@paulbalaji paulbalaji requested a review from Xaroz as a code owner February 17, 2026 13:07
@vercel
Copy link

vercel bot commented Feb 17, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hyperlane-explorer Ready Ready Preview, Comment Feb 17, 2026 1:31pm

Request Review

@paulbalaji paulbalaji marked this pull request as draft February 17, 2026 13:07
@socket-security
Copy link

socket-security bot commented Feb 17, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedjest-environment-jsdom@​29.7.01001006690100
Addedtorph@​0.0.5721009991100

View full report

@socket-security
Copy link

socket-security bot commented Feb 17, 2026

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: npm entities is 91.0% likely obfuscated

Confidence: 0.91

Location: Package overview

From: pnpm-lock.yamlnpm/jest-environment-jsdom@29.7.0npm/entities@6.0.1

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/entities@6.0.1. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 02f1b52a5a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@coderabbitai
Copy link

coderabbitai bot commented Feb 17, 2026

📝 Walkthrough

Walkthrough

A new SafeTextMorph error boundary component is introduced to gracefully handle TextMorph rendering failures. The component wraps dynamic text across multiple pages, with supporting tests and configuration updates. Dependencies for torph and jsdom testing are added; TypeScript module resolution strategy is adjusted.

Changes

Cohort / File(s) Summary
New Error Boundary Component
src/components/SafeTextMorph.tsx, src/components/SafeTextMorph.test.tsx
Introduces SafeTextMorph component with internal error boundary that catches TextMorph failures and falls back to plain text rendering. Includes four test cases covering normal rendering, error handling, empty strings, and type coercion.
SafeTextMorph Integration
src/components/search/SearchFilterBar.tsx, src/features/messages/MessageDetails.tsx, src/features/messages/MessageTable.tsx
Wraps dynamic label and chain display text with SafeTextMorph across three components for consistent error handling during text morphing animations.
Dependencies & Configuration
package.json, tsconfig.json
Adds torph (0.0.5) runtime dependency and jest-environment-jsdom (^30.2.0) dev dependency. Updates TypeScript moduleResolution from "node" to "bundler" for esm module ecosystem compatibility.

Sequence Diagram(s)

sequenceDiagram
    participant Parent as Parent Component
    participant SafeTextMorph
    participant ErrorBoundary
    participant TextMorph as TextMorph Library
    participant Fallback as Fallback Span

    Parent->>SafeTextMorph: Render with children
    SafeTextMorph->>ErrorBoundary: Wrap TextMorph
    ErrorBoundary->>TextMorph: Attempt to render animation
    
    alt Animation Success
        TextMorph-->>ErrorBoundary: Renders morphed text
        ErrorBoundary-->>SafeTextMorph: Display animation
        SafeTextMorph-->>Parent: Show morphed content
    else Animation Error
        TextMorph->>ErrorBoundary: Throws error
        ErrorBoundary->>Fallback: Catch error & render fallback
        Fallback-->>SafeTextMorph: Plain text in span
        SafeTextMorph-->>Parent: Show plain text (safe)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

Like an ogre's got layers, this code's got protection,

SafeTextMorph wraps the text in a safety reflection—

When animations stumble and animations fall,

The fallback's got you covered, plain text for all! 🎭✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately describes the main change: adding the torph library for text morphing animations, which is the primary purpose of this changeset.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/torph-text-animations

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (5)
src/features/messages/MessageTable.tsx (2)

100-104: Lines exceed 100-character width — break 'em up like layers of an onion.

These lines are quite long. The coding guidelines say to maintain 100 character line width. Consider reformatting for readability.

🧅 Suggested formatting
-        <div className={styles.iconText}><SafeTextMorph>{getChainDisplayName(mp, originChainName, true)}</SafeTextMorph></div>
+        <div className={styles.iconText}>
+          <SafeTextMorph>
+            {getChainDisplayName(mp, originChainName, true)}
+          </SafeTextMorph>
+        </div>

Same for the destination line (line 104).

As per coding guidelines: "Use single quotes, trailing commas, and maintain 100 character line width."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/messages/MessageTable.tsx` around lines 100 - 104, The two long
JSX lines inside MessageTable should be wrapped to stay under 100 chars and use
the project's string style: split the LinkCell and its children onto multiple
lines (reference LinkCell, ChainLogo, SafeTextMorph, getChainDisplayName, and
styles.iconText) so each prop and child sits on its own line; convert
double-quoted JSX string props (e.g. aClasses and className) to single quotes
and ensure any trailing commas are applied where appropriate in surrounding JS
objects; specifically reformat both the origin LinkCell and the destination
LinkCell blocks so ChainLogo and the div with SafeTextMorph are on their own
indented lines under the parent LinkCell.

46-48: Existing code nit: manual string concatenation for className instead of clsx().

This isn't part of your change, but since you're in the neighborhood — line 46-48 uses template string concatenation for conditional classes instead of clsx(), which is already imported elsewhere in the project and recommended by the coding guidelines.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/messages/MessageTable.tsx` around lines 46 - 48, Replace the
manual template-string concatenation used in the element's className (the string
that includes "relative cursor-pointer border-b ..." and the conditional
isFetching && 'blur-xs') with a call to clsx so conditional classes are handled
cleanly; update the JSX to use className={clsx('relative cursor-pointer border-b
border-blue-50 last:border-0 hover:bg-pink-50 active:bg-pink-100 transition-all
duration-500', { 'blur-xs': isFetching })} and ensure clsx is imported (or use
the existing project clsx import) so the conditional class toggles correctly.
src/components/SafeTextMorph.test.tsx (1)

53-68: Good fallback test — consider verifying the console.error was actually called.

You're spying on console.error and mocking it (good, keeps test output clean), but you never assert it was called. Since the componentDidCatch explicitly logs 'TextMorph error:', you could add a quick assertion to confirm the error boundary's logging path works as expected.

🧅 Optional assertion
     expect(span).not.toBeNull();
 
+    expect(consoleSpy).toHaveBeenCalledWith(
+      'TextMorph error:',
+      expect.any(Error),
+    );
     consoleSpy.mockRestore();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/SafeTextMorph.test.tsx` around lines 53 - 68, Add an assertion
to the test in SafeTextMorph.test.tsx to verify the console.error spy was called
when the error boundary handled the thrown error: after rendering the
SafeTextMorph with shouldThrow = true, assert that the jest spy on console.error
(created as consoleSpy) was called and that at least one call includes the
expected log string used in SafeTextMorph's componentDidCatch (e.g., 'TextMorph
error:'); then restore the spy as already done. This ensures the error
boundary's logging path in SafeTextMorph is exercised.
src/features/messages/MessageDetails.tsx (1)

103-110: SafeTextMorph on a mostly-static heading — donkey work for little payoff.

This heading text (ICA Message <id> to <chain>) is set once per page load and doesn't change while the user views the page. The morphing animation won't be visible since there's no text transition happening. It's harmless, but it adds a component boundary and error boundary overhead for no visual benefit.

Consider whether you actually want the animation here, or if a plain string suffices. The StatusHeader usage on line 195 makes much more sense since that text actually transitions between states.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/messages/MessageDetails.tsx` around lines 103 - 110, The
SafeTextMorph wrapper around the static heading in MessageDetails is unnecessary
overhead; replace the <SafeTextMorph> around the computed heading text with a
plain string/JSX expression so the h2 renders the static text directly (keep the
existing interpolation using isIcaMsg, trimToLength(msgId, 6), and
getChainDisplayName(multiProvider, destinationChainName)); if you still want
animation for dynamic state changes, keep SafeTextMorph only where text actually
transitions (e.g., StatusHeader) and remove the SafeTextMorph import/usage from
the top heading in MessageDetails.
package.json (1)

37-37: torph@0.0.5 is a pre-release dependency with legitimate stability concerns.

This package is actively used in SafeTextMorph but exists in a pre-release state. While it's resolvable via the pnpm lock file, it doesn't appear on the public npm registry, which means it could be sourced from a private registry or has been unpublished—adding to the risk of it quietly disappearing. Pre-release versions like 0.0.x are prone to breaking changes with any version bump and typically lack extensive community vetting.

Consider monitoring this dependency or reaching out to the maintainers to understand their stability roadmap.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` at line 37, The dependency "torph@0.0.5" used by SafeTextMorph
is a pre-release/unpublished package and poses stability risk; update
package.json by either replacing "torph" with a stable alternative or
pinning/locking it more safely (e.g., move to optionalDependency or add a strict
resolution entry) and add a comment and monitoring strategy; locate the
reference to "torph" in package.json and SafeTextMorph usage, then either swap
in a vetted package, vendor the required code into the repo, or add a note and a
task to contact the maintainers and add CI/alerting to detect future removals or
version bumps.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@package.json`:
- Around line 59-60: The jest and jest-environment-jsdom majors are mismatched:
align their major versions in package.json by either updating "jest" to a ^30.x
release to match "jest-environment-jsdom" or downgrading
"jest-environment-jsdom" to ^29.x to match "jest"; edit the dependency entry for
"jest" or "jest-environment-jsdom" accordingly, run your package manager to
update lockfiles (npm/yarn/pnpm install) and rerun tests; if you choose to bump
jest to ^30.x, ensure Node meets the required range (>=18.14.0 or specified
supported versions) before committing.

---

Nitpick comments:
In `@package.json`:
- Line 37: The dependency "torph@0.0.5" used by SafeTextMorph is a
pre-release/unpublished package and poses stability risk; update package.json by
either replacing "torph" with a stable alternative or pinning/locking it more
safely (e.g., move to optionalDependency or add a strict resolution entry) and
add a comment and monitoring strategy; locate the reference to "torph" in
package.json and SafeTextMorph usage, then either swap in a vetted package,
vendor the required code into the repo, or add a note and a task to contact the
maintainers and add CI/alerting to detect future removals or version bumps.

In `@src/components/SafeTextMorph.test.tsx`:
- Around line 53-68: Add an assertion to the test in SafeTextMorph.test.tsx to
verify the console.error spy was called when the error boundary handled the
thrown error: after rendering the SafeTextMorph with shouldThrow = true, assert
that the jest spy on console.error (created as consoleSpy) was called and that
at least one call includes the expected log string used in SafeTextMorph's
componentDidCatch (e.g., 'TextMorph error:'); then restore the spy as already
done. This ensures the error boundary's logging path in SafeTextMorph is
exercised.

In `@src/features/messages/MessageDetails.tsx`:
- Around line 103-110: The SafeTextMorph wrapper around the static heading in
MessageDetails is unnecessary overhead; replace the <SafeTextMorph> around the
computed heading text with a plain string/JSX expression so the h2 renders the
static text directly (keep the existing interpolation using isIcaMsg,
trimToLength(msgId, 6), and getChainDisplayName(multiProvider,
destinationChainName)); if you still want animation for dynamic state changes,
keep SafeTextMorph only where text actually transitions (e.g., StatusHeader) and
remove the SafeTextMorph import/usage from the top heading in MessageDetails.

In `@src/features/messages/MessageTable.tsx`:
- Around line 100-104: The two long JSX lines inside MessageTable should be
wrapped to stay under 100 chars and use the project's string style: split the
LinkCell and its children onto multiple lines (reference LinkCell, ChainLogo,
SafeTextMorph, getChainDisplayName, and styles.iconText) so each prop and child
sits on its own line; convert double-quoted JSX string props (e.g. aClasses and
className) to single quotes and ensure any trailing commas are applied where
appropriate in surrounding JS objects; specifically reformat both the origin
LinkCell and the destination LinkCell blocks so ChainLogo and the div with
SafeTextMorph are on their own indented lines under the parent LinkCell.
- Around line 46-48: Replace the manual template-string concatenation used in
the element's className (the string that includes "relative cursor-pointer
border-b ..." and the conditional isFetching && 'blur-xs') with a call to clsx
so conditional classes are handled cleanly; update the JSX to use
className={clsx('relative cursor-pointer border-b border-blue-50 last:border-0
hover:bg-pink-50 active:bg-pink-100 transition-all duration-500', { 'blur-xs':
isFetching })} and ensure clsx is imported (or use the existing project clsx
import) so the conditional class toggles correctly.

Only wrap the dynamic currentLabel value when a status filter is
selected, not the static fallback text.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant